namespace ScrumSprintMonitor.Implementation
{
	#region #using Directives

	using System;
	using System.Diagnostics;
	using System.Threading;

	using ContinuousLinq.Reactive;

	using Infrastructure;

	#endregion

	public abstract class DataStreamObjectSerializer : ReactiveObject,
	                                                   IDisposable
	{
		protected readonly IDataStreamProvider streamProvider;
		private bool disposed;
		protected bool initialized;
		private SynchronizationContext syncContext;

		protected DataStreamObjectSerializer(IDataStreamProvider streamProvider)
		{
			if (streamProvider == null)
			{
				throw new ArgumentNullException("streamProvider");
			}

			this.streamProvider = streamProvider;
		}

		protected bool Disposed
		{
			[DebuggerStepThrough]
			get { return this.disposed; }
		}

		~DataStreamObjectSerializer()
		{
			Dispose(false);
		}

		protected void EnsureObjectIsRead()
		{
			if (!this.initialized)
			{
				this.streamProvider.StreamChanged += OnStreamProviderStreamChanged;

				try
				{
					ReadObjectFromStream();
				}
				finally
				{
					this.syncContext = SynchronizationContext.Current;
					this.initialized = true;
				}
			}
		}

		protected void GuardDisposed()
		{
			if (Disposed)
			{
				throw new ObjectDisposedException(GetType().Name);
			}
		}

		protected abstract void ReadObjectFromStream();

		protected void FreezeStreamChangeNotifications()
		{
			this.streamProvider.StreamChanged -= OnStreamProviderStreamChanged;
		}

		protected void UnfreezeStreamChangeNotifications()
		{
			this.streamProvider.StreamChanged += OnStreamProviderStreamChanged;
		}

		private void OnStreamProviderStreamChanged(object sender, EventArgs e)
		{
			ReadObjectFromStream();
			this.initialized = true;
		}

		#region Implementation of IDisposable

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		/// <filterpriority>2</filterpriority>
		public void Dispose()
		{
			GC.SuppressFinalize(this);
			Dispose(true);
		}

		protected virtual void Dispose(bool disposing)
		{
			if (!this.disposed)
			{
				this.disposed = true;
				this.streamProvider.StreamChanged -= OnStreamProviderStreamChanged;

				var disposable = this.streamProvider as IDisposable;
				if (disposable != null)
				{
					disposable.Dispose();
				}
			}
		}

		#endregion

		#region Implementation of INotifyPropertyChanged

		private void RaisePropertyChanged(object state)
		{
			OnPropertyChanged((string) state);
		}

		protected void AsyncRaisePropertyChanged(string propertyName)
		{
			if (!this.initialized)
			{
				return;
			}

			if (this.syncContext == null || !Thread.CurrentThread.IsBackground)
			{
				OnPropertyChanged(propertyName);
			}
			else
			{
				this.syncContext.Post(RaisePropertyChanged, propertyName);
			}
		}

		#endregion
	}
}